﻿using CloudNimble.SimpleMessageBus.Core;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;

namespace CloudNimble.SimpleMessageBus.Publish
{

    /// <summary>
    /// Manages the process of publishing MessageBus messages to the local file system.
    /// </summary>
    public class FileSystemMessagePublisher : IMessagePublisher
    {

        #region Private Members

        private readonly FileSystemOptions _options;

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new instance of the <see cref="FileSystemMessagePublisher"/>.
        /// </summary>
        /// <param name="options"></param>
        /// <exception cref="ArgumentNullException"><paramref name="options"/> is <see langword="null" />.</exception>
        /// <exception cref="ArgumentException">The connection string you specified was not found in the ConnectionStrings collection.</exception>
        public FileSystemMessagePublisher(IOptions<FileSystemOptions> options)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options), "Please register a FileSystemOptions instance with your DI container.");
            }
            if (string.IsNullOrWhiteSpace(options.Value.RootFolder))
            {
                throw new ArgumentNullException(nameof(options.Value.RootFolder), "Please specify the path to the folder that will store queue items.");
            }

            _options = options.Value;

            if (!Directory.Exists(_options.QueueFolderPath))
            {
                Directory.CreateDirectory(_options.QueueFolderPath);
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Publishes the specified <see cref="IMessage"/> to a queue.
        /// </summary>
        /// <param name="message">The <see cref="IMessage"/> to wrap in a <see cref="MessageEnvelope"/> and publish to the queue.</param>
        /// <param name="isSystemGenerated">Specifies whether or not the event was generated by the system. (Not currently used).</param>
        /// <returns>A <see cref="Task"/> reference for the asynchronous function.</returns>
        public async Task PublishAsync(IMessage message, bool isSystemGenerated = false)
        {
            await Task.Run(() =>
            {
                //RWM: Wrap the entity in a MessageEnvelope.
                var envelope = new MessageEnvelope(message)
                {
                    Id = Guid.NewGuid(),
                    DatePublished = DateTimeOffset.UtcNow
                };

                var payload = JsonConvert.SerializeObject(envelope);
                var filePath = Path.Combine(_options.QueueFolderPath, $"{envelope.Id}.json");

                //RWM: If it's a network path, the write may be streamed and the file in use before the Dispatcher picks it up.
                //     So in that case, let's write to a temp file and then rename it when it's done.
                if (_options.IsNetworkPath)
                {
                    Trace.TraceInformation("Network path detected.");
                    var tempFile = Path.Combine(_options.QueueFolderPath, $"{envelope.Id}.tmpmsg");
                    File.WriteAllText(tempFile, payload);
                    File.Move(tempFile, filePath);
                }
                else
                {
                    File.WriteAllText(filePath, payload);
                }

            }).ConfigureAwait(false);
        }

        #endregion

    }

}